package com.autonavi.yunda.yunji.core.service.dbfactory;

import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.core.enums.DBType;
import com.autonavi.yunda.yunji.core.enums.DatasourceType;
import com.autonavi.yunda.yunji.core.service.IDataSourceConfigService;
import com.autonavi.yunda.yunji.core.vo.datasource.config.BaseDataConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author cizhong.wcs
 * @date 2022/9/2 10:47
 */
@Slf4j
public abstract class AbstractDataSourceFactory<T> {

    @Value("${yunji.init.datasource.lazy:false}")
    protected Boolean lazyInit;
    @Resource
    protected IDataSourceConfigService dataSourceConfigService;
    /**
     * 数据源实例容器
     */
    protected final Map<String, T> dataSourceVOMap = new ConcurrentHashMap<>();

    public abstract DBType getDbType();

    public abstract T createDataSource(DatasourceType dataSourceType, String dataSourceConfig);

    public abstract void close(T dataSource);

    @PostConstruct
    protected void initDataSourceCache() {
        if (lazyInit) {
            log.info("{} initDataSourceCache lazy!!", getDbType());
            return;
        }
        this.dataSourceConfigService.findByDataSourceTypeList(getDbType().getDatasourceTypes())
                .forEach(result -> {
                    try {
                        String dataSourceName = result.getLeft();
                        DatasourceType datasourceType = result.getMiddle();
                        String dataSourceConfig = result.getRight();
                        log.info("createDataSource name:{},type:{}", dataSourceName, dataSourceConfig);
                        T dataSource = createDataSource(datasourceType, dataSourceConfig);
                        dataSourceVOMap.put(dataSourceName, dataSource);
                    } catch (Exception e) {
                        log.info("数据源初始化失败 name:{},type = {}", result.getLeft(), result.getMiddle(), e);
                    }
                });
        log.info("initDataSourceCache end");
    }

    /**
     * 获取数据源工厂实例
     * 包含ossClient(OSS数据源)、RedisTemplate(Redis数据源)、JdbcTemplate(SQL数据源)等
     */
    public T getInstance(String dataSourceName) throws AmapException {
        if (!StringUtils.hasText(dataSourceName)) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "dataSourceName 不可为空！");
        }
        log.debug("getInstance:" + dataSourceName);
        if (!dataSourceVOMap.containsKey(dataSourceName)) {
            synchronized (this) {
                if (!dataSourceVOMap.containsKey(dataSourceName)) {
                    createDataSourceByName(dataSourceName);
                    T dbObj = dataSourceVOMap.get(dataSourceName);
                    if (dbObj != null) {
                        return dbObj;
                    } else {
                        log.error("dataSourceName " + dataSourceName + " return null!");
                        throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "conf dataSourceName is not match");
                    }
                }
            }
        }
        return dataSourceVOMap.get(dataSourceName);
    }

    /**
     * 数据库配置刷新
     */
    protected void createDataSourceByName(String dataSourceName) {
        try {
            if (!StringUtils.hasText(dataSourceName)) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "创建的数据源名称为空！");
            }
            Optional<Pair<DatasourceType, String>> optionalDatasource
                    = this.dataSourceConfigService.findDataSourceConfigByName(dataSourceName);
            if (optionalDatasource.isEmpty()) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "数据源配置不存在");
            }
            DatasourceType datasourceType = optionalDatasource.get().getLeft();
            String config = optionalDatasource.get().getRight();
            dataSourceVOMap.put(dataSourceName, createDataSource(datasourceType, config));
        } catch (Exception e) {
            log.info("getInstanceByDb:" + e.getMessage(), e);
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "数据源创建失败:" + e.getMessage(), e);
        }
    }

    public void removeDataSource(String dataSourceName) {
        T dataSource = dataSourceVOMap.get(dataSourceName);
        dataSourceVOMap.remove(dataSourceName);
        if (dataSource != null) {
            close(dataSource);
        }
    }

    public void testConnection(String dataSourceName) {
        throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "暂不支持测试连接");
    }

    public Pair<DatasourceType, String> findDataSourceConfigByName(String dataSourceName) {
        return dataSourceConfigService.findDataSourceConfigByName(dataSourceName)
                .orElseThrow(() -> new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "数据源配置不存在"));
    }

    public Set<String> getDatasourceOperators(String dbConfig) {
        BaseDataConfig config = JsonUtils.from(dbConfig, BaseDataConfig.class);
        if (config != null && !CollectionUtils.isEmpty(config.sqlOperator)) {
            return config.sqlOperator;
        }
        return Set.of("select");
    }
}
